/*  Copyright © 2009 Blake Hegerle -*- C++ -*- */

/*  This file is part of metal.
 
    Metal is free software: you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    General Public License for more details.
 
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see
    <http://www.gnu.org/licenses/>.

*/

#pragma once

#include <type_traits>

#include "forward.h"
#include "util.h"
#include "meta/type.h"
#include "meta/binding.h"

namespace metal {
  namespace type_detail {
    typedef unsigned char type_code_base;

    const type_code_base size_mask = 0x7;
    const type_code_base int_flag = 0x8;
    const type_code_base signed_flag = 0x10;
    const type_code_base object_flag = 0x20;
    const type_code_base heterogeneous_flag = 0x40;
    const type_code_base bool_or_char_flag = 0x80;
  }

  enum class type_code : type_detail::type_code_base {
    boolean = 0 | type_detail::bool_or_char_flag,
    character = 1 | type_detail::bool_or_char_flag,
    uint8 = 0 | type_detail::int_flag,
    int8 = 0 | type_detail::signed_flag | type_detail::int_flag,
    uint16 = 1 | type_detail::int_flag,
    int16 = 1 | type_detail::signed_flag | type_detail::int_flag,
    uint32 = 2 | type_detail::int_flag,
    int32 = 2 | type_detail::signed_flag | type_detail::int_flag,
    uint64 = 3 | type_detail::int_flag,
    int64 = 3 | type_detail::signed_flag | type_detail::int_flag,
    float32 = 2 | type_detail::signed_flag,
    float64 = 3 | type_detail::signed_flag,
    float128 = 4 | type_detail::signed_flag,
    array = type_detail::object_flag,
    user_defined = type_detail::heterogeneous_flag | type_detail::object_flag,
    function = type_detail::heterogeneous_flag
  };
  
  class member;

  namespace detail {
    template<typename T>
    struct type_cmp {
      bool operator()(T l, T r) {
	return *l == *r;
      };
    };
  }

  class type {
    typedef std::vector<member> members;
    typedef std::vector<type_ptr> types;
  public:
    typedef members::const_iterator const_iterator;
    
    type(type_code code) : c(code) {}
    type(type_code code, type_ptr et) 
      : c(code), et(et) {}

    template<typename I>
    type(type_code code, I begin, I end) : c(code), m(begin, end) {}

    template<typename I>
    type(bool function, type_code code, I begin, I end) 
      : c(code), sig(begin, end)
    {}
    
    bool operator==(const type& t) const {
      if(c != t.c)
	return false;

      if(c == type_code::array) 
	return *et == *t.et;
      else if(c == type_code::user_defined) {
	return m.size() == t.m.size()
	  && std::equal(m.begin(), m.end(), t.m.begin());
      } else if(c == type_code::function) {
	auto seq = std::equal(sig.begin(), 
			      sig.end(), 
			      t.sig.begin(), 
			      detail::type_cmp<type_ptr>());
	return sig.size() == t.sig.size()
	  && seq;
      } 

      return true;
    }

    type_code code() const { return c; }
    type_ptr element_type() const { return et; }

    bool is_float() const { 
      return is_signed() && !is_int();
    }

    bool is_int() const {
      return (int)c & type_detail::int_flag;
    }

    bool is_signed() const {
      return (int)c & type_detail::signed_flag;
    }

    bool is_composite() const { 
      return c == type_code::array;
    }

    int size() const {
      auto shift = (int)c & type_detail::size_mask;
      return 1 << shift;
    }

    const_iterator begin() const { return m.begin(); }
    const_iterator end() const { return m.end(); }

    types::const_iterator signature_begin() const { return sig.begin(); }
    types::const_iterator signature_end() const { return sig.end(); }
  private:
    type_code c;


    // union {
    type_ptr et;
    members m;
    types sig;    
    // }
  };

class sdfx {
	  bool operator()(type_ptr l, type_ptr r) {
	    return *l == *r;
	  }
	};

  class member {
  public:
    member(const std::string &n, const type_ptr &type)
      : n(n), t(type) { }

    bool operator==(const member &m) const { 
      return name() == m.name()
	&& *type() == *m.type();
    }

    const std::string &name() const { return n; }
    type_ptr type() const { return t; }
  private:
    std::string n;
    type_ptr t;
  };

  template<typename T>
  struct primitive_type_code {
    static const type_code p_code;
  };

  // It's system dependent whether a local value of a primitive type
  // can represent the range of values of a hosted type, c.f. float128
  // which has little hardware support.

#define PARALLEL_P_CODE(t, c) template<> struct primitive_type_code<t> { static const type_code p_code = type_code::c; };
  PARALLEL_P_CODE(bool, boolean);
  PARALLEL_P_CODE(char, character);
  PARALLEL_P_CODE(signed char, int8);
  PARALLEL_P_CODE(unsigned char, uint8);
  PARALLEL_P_CODE(short, int16);
  PARALLEL_P_CODE(unsigned short, uint16);
  PARALLEL_P_CODE(int, int32);
  PARALLEL_P_CODE(unsigned int, uint32);
  PARALLEL_P_CODE(long, int32);
  PARALLEL_P_CODE(unsigned long, uint32);
  PARALLEL_P_CODE(long long, int64);
  PARALLEL_P_CODE(unsigned long long, uint64);
  PARALLEL_P_CODE(float, float32);
  PARALLEL_P_CODE(double, float64);
  PARALLEL_P_CODE(long double, float128);

  template<typename T>
  type_ptr lower();

  namespace structure_detail {
    using namespace std;

    template<typename S>
    struct lower {
      static void to_list(vector<member> &);
    };

    template<>
    struct lower<meta::structure<> > {
      static void to_list(vector<member> &memberList) {}  
    };

    template<typename I, typename T, typename... Members>
    struct lower<
      meta::structure<
	meta::binding<I, T>, 
	Members...
	>
      >
    {
      static void to_list(vector<member> &memberList) {
	type_ptr t = ::metal::lower<T>();
	member m(name_of<I>(), t);
	memberList.push_back(m);
	lower<meta::structure<Members...> >::to_list(memberList);
      }
    };
  }

  namespace function_detail {
    using namespace std;

    template<typename S>
    struct lower {
      static void to_list(vector<type_ptr> &);
    };

    template<>
    struct lower<list<> > {
      static void to_list(vector<type_ptr> &) {}
    };
    
    template<typename T, typename... Types>
    struct lower<list<T, Types...> > {
      static void to_list(vector<type_ptr> &typeList) {
	type_ptr t = ::metal::lower<T>();
	typeList.push_back(t);
	lower<list<Types...> >::to_list(typeList);
      }
    };
  }

  // the lower method converts a meta::type into a runtime type
  template<typename T>
  type_ptr lower() {
    using namespace std;

    type_ptr t;

    typedef typename meta::element_type<T>::type e_type;

    if(is_arithmetic<T>::value) 
      t = type_ptr(new type(primitive_type_code<T>::p_code));
    
    if(!is_void<e_type>::value)
      t = type_ptr(new type(type_code::array, lower<e_type>()));

    if(meta::is_structure<T>::value) {
      vector<member> memberList;
      structure_detail::lower<T>::to_list(memberList);
      t = type_ptr(new type(type_code::user_defined, 
			    memberList.begin(), 
			    memberList.end()));
    }

    if(is_list<T>::value) {
      vector<type_ptr> typeList;
      function_detail::lower<T>::to_list(typeList);
      t = type_ptr(new type(true,
			    type_code::function,
			    typeList.begin(),
			    typeList.end()));
    }

    return t;
  }
}
